﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;

namespace Vulild.AopDecoration
{
    public class AopDecorationGenerator
    {
        Dictionary<string, FieldBuilder>? properties = new();

        public Type? GenerateType<TImp, TIns>()
        {
            Type typeIns = typeof(TIns);
            Type typeImp = typeof(TImp);
            Type typeAop = typeof(IAopDecoration<TImp>);
            AssemblyName an = new AssemblyName($"{typeIns.FullName}.AopDecoration");//创建程序集名称

            AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);//创建程序集

            ModuleBuilder mb = ab.DefineDynamicModule(an.FullName + ".dll");//创建模块

            TypeBuilder tb = mb.DefineType($"{an.FullName}.{typeIns.Name}AopDecoration");//创建类

            //添加继承
            tb.AddInterfaceImplementation(typeAop);
            tb.AddInterfaceImplementation(typeImp);

            //创建aop抽象属性
            var pis = typeAop.GetProperties();
            properties = new Dictionary<string, FieldBuilder>();
            foreach (var pi in pis)
            {
                var fb = CreateProperty(tb, pi.Name, pi.PropertyType);
                properties[pi.Name] = fb;
            }

            var insFb = properties["Instance"];

            //创建被包装实例对应的构造函数
            var cb = CreateConstructor(tb, /*insFb,*/ typeIns);

            //创建被包装实例的对应方法，只开放抽象方法，实例方法不开放
            var insMbs = CreateInsMethods(tb, insFb, typeImp, typeIns);

            CreateInsPropertys(tb, insFb, typeImp, insMbs);

            //重写ToString，使调试看起来与实际类型一致
            var miToString = typeIns.GetMethod("ToString");
            if (miToString != null)
            {
                CreateInsMethod(tb, insFb, miToString);
            }

            return tb.CreateType();
        }

        /// <summary>
        /// 创建构造函数，与被包装实例一致
        /// </summary>
        /// <param name="tb"></param>
        /// <param name="fb"></param>
        /// <param name="insType"></param>
        /// <returns></returns>
        private ConstructorBuilder? CreateConstructor(TypeBuilder tb, Type insType)
        {
            var ci = insType.GetConstructors().FirstOrDefault();
            if (ci != null)
            {
                var ciParams = ci.GetParameters();
                var ciAttr = MethodAttributes.Public;
                var ciCC = CallingConventions.Standard;
                var ciBuilder = tb.DefineConstructor(ciAttr, ciCC, ciParams.Select(a => a.ParameterType).ToArray());

                var il = ciBuilder.GetILGenerator();

                InitInstance(il, ciParams, ci);

                InitFilters(il);

                il.Emit(OpCodes.Ret);

                return ciBuilder;
            }
            return null;
        }

        /// <summary>
        /// 实例化Instance属性。
        /// </summary>
        /// <param name="il"></param>
        /// <param name="ciParams"></param>
        /// <param name="ci"></param>
        void InitInstance(ILGenerator il, ParameterInfo[] ciParams, ConstructorInfo ci)
        {
            if (properties != null && properties.ContainsKey("Instance"))
            {
                var fb = properties["Instance"];
                il.Emit(OpCodes.Ldarg_0);
                for (int i = 1; i < ciParams.Length + 1; i++)
                {
                    il.Emit(OpCodes.Ldarg, i);
                }

                il.Emit(OpCodes.Newobj, ci);
                il.Emit(OpCodes.Stfld, fb);
            }
        }

        /// <summary>
        /// 实例化筛选器
        /// </summary>
        /// <param name="il"></param>
        void InitFilters(ILGenerator il)
        {
            var filterCi = typeof(AopFilterCollection).GetConstructor(Type.EmptyTypes);

            if (filterCi != null)
            {
                InitFilter("ExecutingFilters", il, filterCi);

                InitFilter("ExecutedFilters", il, filterCi);

                InitFilter("ExceptionFilters", il, filterCi);
            }
        }

        void InitFilter(string name, ILGenerator il, ConstructorInfo ci)
        {
            if (properties != null && properties.ContainsKey(name))
            {
                var fb = properties[name];

                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Newobj, ci);
                il.Emit(OpCodes.Stfld, fb);
            }
        }

        /// <summary>
        /// 创建代理方法，调用被包装实例的对应方法
        /// </summary>
        /// <param name="tb"></param>
        /// <param name="fb"></param>
        /// <param name="implType"></param>
        private Dictionary<string, MethodBuilder> CreateInsMethods(TypeBuilder tb, FieldBuilder fb, Type implType, Type insType)
        {
            Dictionary<string, MethodBuilder> dic = new Dictionary<string, MethodBuilder>();
            var mis = implType.GetMethods();
            foreach (var mi in mis)
            {
                var mb = CreateInsMethod(tb, fb, mi, insType);
                dic.Add(mi.Name, mb);
            }
            return dic;
        }

        /// <summary>
        /// 创建被包装实例的对应方法
        /// </summary>
        /// <param name="tb"></param>
        /// <param name="fb"></param>
        /// <param name="mi"></param>
        /// <returns></returns>
        private MethodBuilder CreateInsMethod(TypeBuilder tb, FieldBuilder fb, MethodInfo mi, Type? insType = null)
        {
            //订阅方法的公开权限。继承接口的方法必须是MethodAttributes.Public | MethodAttributes.Virtual 
            var attrs = MethodAttributes.Public | MethodAttributes.Virtual;

            var miParams = mi.GetParameters();
            MethodBuilder mb = tb.DefineMethod(mi.Name, attrs, mi.ReturnType, miParams.Select(a => a.ParameterType).ToArray());//创建方法

            if (mi.IsGenericMethod)
            {
                mb.DefineGenericParameters(mi.GetGenericArguments().Select(a => a.Name).ToArray());
            }

            var il = mb.GetILGenerator();

            #region 调用筛选器

            var lbContext = CreateContext(il, insType);
            if (lbContext != null)
            {
                SetContextMember(lbContext, il, mi.Name);

                SetContextParam(miParams, il, lbContext);

                SetContextInstance(lbContext, il, fb);

                ExecuteFilters("ExecutingFilters", lbContext, il);
            }

            #endregion

            il.BeginExceptionBlock();//try
            il.Emit(OpCodes.Ldarg, 0);//调用this指针
            il.Emit(OpCodes.Ldfld, fb);//调用this.Instance

            //加载当前方法的参数，传递给被包装实例的对应方法。
            for (int i = 1; i <= miParams.Length; i++)
            {
                il.Emit(OpCodes.Ldarg, i);
            }
            il.Emit(OpCodes.Callvirt, mi);
            LocalBuilder? result = null;
            if (mi.ReturnType != typeof(void))
            {
                result = il.DeclareLocal(mi.ReturnType);//声明字符串变量
                il.Emit(OpCodes.Stloc, result);//方法的返回值赋值给result；
            }
            il.BeginCatchBlock(typeof(Exception));//catch

            if (lbContext != null)
            {
                SetContextException(lbContext, il);
                ExecuteFilters("ExceptionFilters", lbContext, il);
            }

            il.EndExceptionBlock();
            if (lbContext != null)
            {
                if (result != null)
                {
                    SetContextResult(lbContext, il, result);
                }
                ExecuteFilters("ExecutedFilters", lbContext, il);
            }

            if (result != null)
            {
                il.Emit(OpCodes.Ldloc, result);//加载返回值
            }
            il.Emit(OpCodes.Ret);

            return mb;
        }

        /// <summary>
        /// 调用将参数给aopcontext.AddParam 将参数给aopcontext赋值
        /// </summary>
        /// <param name="miParams"></param>
        /// <param name="il"></param>
        /// <param name="lbContext"></param>
        void SetContextParam(ParameterInfo[] miParams, ILGenerator il, LocalBuilder lbContext)
        {
            var mi = lbContext.LocalType.GetMethod("AddParam");
            if (mi != null && miParams != null)
            {
                for (int i = 0; i < miParams.Length; i++)
                {
                    il.Emit(OpCodes.Ldloc, lbContext);
                    il.Emit(OpCodes.Ldstr, miParams[i].Name);
                    il.Emit(OpCodes.Ldarg, i + 1);
                    il.Emit(OpCodes.Box, miParams[i].ParameterType.IsByRef ? miParams[i].ParameterType.GetElementType() : miParams[i].ParameterType);
                    il.Emit(OpCodes.Callvirt, mi);
                }
            }
        }

        /// <summary>
        /// 将当前被包装实例赋值给AopContext.Instance
        /// </summary>
        /// <param name="lbContext"></param>
        /// <param name="il"></param>
        /// <param name="fb"></param>
        void SetContextInstance(LocalBuilder lbContext, ILGenerator il, FieldBuilder fb)
        {
            var setIns = lbContext.LocalType.GetMethod("set_Instance");
            if (setIns != null)
            {
                il.Emit(OpCodes.Ldloc, lbContext);
                il.Emit(OpCodes.Ldarg, 0);
                il.Emit(OpCodes.Ldfld, fb);
                il.Emit(OpCodes.Callvirt, setIns);
            }
        }

        /// <summary>
        /// 给AopContext.MemberName赋值
        /// </summary>
        /// <param name="lbContext"></param>
        /// <param name="il"></param>
        /// <param name="memberName"></param>
        void SetContextMember(LocalBuilder lbContext, ILGenerator il, string memberName)
        {
            var miMemberName = lbContext.LocalType.GetMethod("set_MemberName");
            if (miMemberName != null)
            {
                il.Emit(OpCodes.Ldloc, lbContext);
                il.Emit(OpCodes.Ldstr, memberName);
                il.Emit(OpCodes.Callvirt, miMemberName);
            }
        }

        void SetContextException(LocalBuilder lbContext, ILGenerator il)
        {
            var miException = lbContext.LocalType.GetMethod("set_Exception");
            if (miException != null)
            {
                var exception = il.DeclareLocal(typeof(Exception));//声明一个exception对象
                il.Emit(OpCodes.Stloc, exception);//将当前捕获的exception赋值给exception对象。
                il.Emit(OpCodes.Ldloc, lbContext);
                il.Emit(OpCodes.Ldloc, exception);//加载exception对象，给下面构造函数使用
                il.Emit(OpCodes.Callvirt, miException);

            }
        }

        /// <summary>
        /// 给AopContext.Result赋值
        /// </summary>
        /// <param name="lbContext"></param>
        /// <param name="il"></param>
        /// <param name="result"></param>
        void SetContextResult(LocalBuilder lbContext, ILGenerator il, LocalBuilder result)
        {
            var miResult = lbContext.LocalType.GetMethod("set_Result");
            if (miResult != null)
            {
                il.Emit(OpCodes.Ldloc, lbContext);
                il.Emit(OpCodes.Ldloc, result);
                il.Emit(OpCodes.Box, result.LocalType);
                il.Emit(OpCodes.Callvirt, miResult);
            }
        }

        /// <summary>
        /// 执行前筛选器
        /// </summary>
        /// <param name="name"></param>
        /// <param name="lb"></param>
        /// <param name="il"></param>
        void ExecuteFilters(string name, LocalBuilder lb, ILGenerator il)
        {
            if (properties != null && properties.ContainsKey(name))
            {
                var executedFilters = properties[name];
                var insFb = properties["Instance"];
                var filterExecute = typeof(AopFilterCollection).GetMethod("Execute");
                if (executedFilters != null && filterExecute != null)
                {
                    var genericExecute = filterExecute.MakeGenericMethod(new Type[] { insFb.FieldType });
                    il.Emit(OpCodes.Ldarg, 0);
                    il.Emit(OpCodes.Ldfld, executedFilters);
                    il.Emit(OpCodes.Ldloc, lb);
                    il.Emit(OpCodes.Callvirt, genericExecute);
                }

            }
        }

        /// <summary>
        /// 创建aopcontext实例
        /// </summary>
        /// <param name="il"></param>
        /// <param name="insType"></param>
        /// <returns></returns>
        LocalBuilder? CreateContext(ILGenerator il, Type? insType)
        {
            if (insType == null)
            {
                return null;
            }
            var contextType = Type.GetType($"{this.GetType().Namespace}.AopContext`1[[{insType.FullName},{insType.Assembly.FullName}]]");
            //var contextType = typeof(AopContext);
            var lb = il.DeclareLocal(contextType);
            var contextCi = contextType.GetConstructor(Type.EmptyTypes);
            if (contextCi == null)
            {
                return null;
            }
            il.Emit(OpCodes.Newobj, contextCi);
            il.Emit(OpCodes.Stloc, lb);
            return lb;
        }

        /// <summary>
        /// 创建代理属性，调用被包装实例对应属性的get，set。
        /// </summary>
        /// <param name="tb"></param>
        /// <param name="fb"></param>
        /// <param name="implType"></param>
        private void CreateInsPropertys(TypeBuilder tb, FieldBuilder fb, Type implType, Dictionary<string, MethodBuilder> insMbs)
        {
            var pis = implType.GetProperties();
            foreach (var pi in pis)
            {
                CreateInsProperty(tb, pi, fb, insMbs);
            }
        }

        /// <summary>
        /// 创建实例属性
        /// </summary>
        /// <param name="tb"></param>
        /// <param name="pi"></param>
        /// <param name="fb"></param>
        /// <returns></returns>
        private PropertyBuilder CreateInsProperty(TypeBuilder tb, PropertyInfo pi, FieldBuilder fb, Dictionary<string, MethodBuilder> insMbs)
        {
            string propertyName = pi.Name;
            Type propertyType = pi.PropertyType;
            PropertyBuilder insProperty = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
            var insAttr = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.HideBySig;

            if (pi.GetMethod != null)
            {
                ////创建属性Get方法
                string getStr = $"get_{propertyName}";
                if (insMbs.ContainsKey(getStr))
                {
                    insProperty.SetGetMethod(insMbs[getStr]);
                }
            }

            if (pi.SetMethod != null)
            {
                ////创建属性Set方法
                string setStr = $"set_{propertyName}";
                if (insMbs.ContainsKey(setStr))
                {
                    insProperty.SetSetMethod(insMbs[setStr]);
                }
            }


            return insProperty;
        }

        /// <summary>
        /// 创建属性，此处用来创建被包装的实例
        /// </summary>
        /// <param name="tb"></param>
        /// <param name="propertyName"></param>
        /// <param name="propertyType"></param>
        /// <returns></returns>
        private FieldBuilder CreateProperty(TypeBuilder tb, string propertyName, Type propertyType)
        {
            //创建字段
            FieldBuilder insField = tb.DefineField($"_{propertyName}", propertyType, FieldAttributes.Private);

            PropertyBuilder insProperty = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
            var insAttr = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.HideBySig;

            //创建属性Get方法
            var insGet = tb.DefineMethod($"get_{propertyName}", insAttr, propertyType, Type.EmptyTypes);
            ILGenerator ilGet = insGet.GetILGenerator();
            ilGet.Emit(OpCodes.Ldarg_0);
            ilGet.Emit(OpCodes.Ldfld, insField);
            ilGet.Emit(OpCodes.Ret);

            //创建属性Set方法
            var insSet = tb.DefineMethod($"set_{propertyName}", insAttr, null, new[] { propertyType });
            ILGenerator ilSet = insSet.GetILGenerator();
            ilSet.Emit(OpCodes.Ldarg_0);
            ilSet.Emit(OpCodes.Ldarg_1);
            ilGet.Emit(OpCodes.Stfld, insField);
            ilGet.Emit(OpCodes.Ret);

            insProperty.SetGetMethod(insGet);
            insProperty.SetSetMethod(insSet);

            return insField;
        }
    }
}
